<?php
/**
 * @usage
 * $ret = HttpSocketClient::post("http://www.baidu.com/", array());
 * var_dump($ret);
 *
 */
class HttpSocketClient 
{
    const METHOD_GET  = "GET";
    const METHOD_POST = "POST";
    const CODE_SOCKET_OPEN_FAIL = 1;
    const CONNECT_TIMEOUT = 1;

    public static $timeoutSec = 1;
    public static $timeoutUsec= 200;

    private static $methods = array(
        self::METHOD_GET,
        self::METHOD_POST,    
    );


    /**
     * @param string $host, http://www.xxx.com/?q=xxx
     * @param array $data
     */
    public static function get($host, $data = array(), $timeout = self::CONNECT_TIMEOUT) 
    {
		return self::request($host, $data,  self::METHOD_GET, $timeout);
	}
	
    public static function post($host, $data = array(), $timeout = self::CONNECT_TIMEOUT) 
    {
		return self::request($host, $data, self::METHOD_POST, $timeout);
	}

    public static function request($host, $data = array(),  $method = self::METHOD_GET, $timeout = self::CONNECT_TIMEOUT) 
    {
        $function_start_time = microtime(true);
        //check method
		$method = strtoupper($method);
        self::checkMethod($method);	

        $parse         = self::parseHost($host);
		$data          = http_build_query($data);

        $start_time = microtime(true);
        $content = self::buildRequestContent($parse, $method, $data);

        $fp = fsockopen($parse['host'], $parse['port'], $errnum, $errstr, $timeout);
        stream_set_timeout($fp, self::$timeoutSec, self::$timeoutUsec);

        if (!$fp)
        {
            throw new Exception("connect socket fail", self::CODE_SOCKET_OPEN_FAIL);
        }

        $stop_time = microtime(true);

        $start_time = microtime(true);

        //write to socket
		fwrite($fp, $content);
        $stop_time = microtime(true);
		$responseText = '';
        $start_time = microtime(true);

        //read from socket
        while ($data = fread($fp, 1024)) 
        {
			$responseText .= $data;
		}

        $stop_time = microtime(true);

        //close socket
		fclose($fp);
	    $ret = self::parseResponse($responseText);	

        $function_stop_time = microtime(true);

		return $ret;
	}

    private static function buildRequestContent(&$parse, $method, $data)
    {
		$contentLengthStr = '';
		$postContent   = '';

        if ($method == self::METHOD_GET) 
        {
			substr($data, 0, 1) == '&' && $data = substr($data, 1);
		    $query         = isset($parse['query']) ? $parse['query'] : '';
			$parse['path'] .= ($query ? '&' : '?').$data;
        } 
        else if ($method == self::METHOD_POST) 
        {
			$contentLengthStr = "Content-length: ".strlen($data)."\r\n";
			$postContent   = $data;
		}

		$write = $method." ".$parse['path']." HTTP/1.0\r\n";
		$write .= "Host: ".$parse['host']."\r\n";
		//$write .= "Content-Type:text/html;charset=UTF8'";
		$write .= "Content-type: application/x-www-form-urlencoded\r\n";
		$write .= $contentLengthStr;
		//$write .= "Connection: Keep-Alive\r\n";
		$write .= "Connection: close\r\n\r\n";
		$write .= $postContent;

        return $write;
    }

    private static function parseResponse($responseText)
    {
        $http_header_str  = substr($responseText, 0, strpos($responseText, "\r\n\r\n"));
        $http_headers = self::parseHttpSocketHeader($http_header_str);
		$responseText = trim(stristr($responseText, "\r\n\r\n"), "\r\n");
        $ret = array();
        $ret["result"] = $responseText;
        $ret["info"]["http_code"]   = isset($http_headers["Http_Code"]) ? $http_headers["Http_Code"] : 0;
        $ret["info"]["headers"]  = $http_headers;
        $ret["result"] = $responseText;

        return $ret;
    }

    private static function parseHost($host)
    {
        //parse host url
		$parse  = parse_url($host);

        if (!$parse) 
        {
            throw new Exception("host is empty");
        }

        if (!isset($parse['port']) || !$parse['port']) 
        {
            $parse['port'] = '80';
        }

		$parse['host'] = str_replace(array('http://', 'https://'), array('', 'ssl://'), $parse['scheme']."://").$parse['host'];
        $parse["path"] = isset($parse["path"]) ? $parse["path"] : '/';
		$query = isset($parse['query']) ? $parse['query'] : '';

        $path = str_replace(array('\\', '//'), '/', $parse['path']);
		$parse['path'] = $query ? $path."?".$query : $path;

        return $parse;
    }
    private static function checkMethod($method)
    {
        if (!in_array($method, self::$methods)) 
        {
            throw new Exception("method is invalid");
        }
    }

    private static function parseHttpSocketHeader($str)
    {
        $slice = explode("\r\n", $str);
        $headers = array();

        foreach ($slice as $v)
        {
            if (false !== strpos($v, "HTTP"))
            {
                $headers["Http_Code"] = self::parseHttpCodeFromSocketHeader($v);
                $headers["Status"] = $v;
            }
            else
            {
                $header_slice = explode(":", $v);
                $headers[$header_slice[0]] = isset($header_slice[1]) ? $header_slice[1] : '';
            }
        }

        return $headers;
    }

    private static function parseHttpCodeFromSocketHeader($str)
    {
        $slice = explode(" ", $str);
        return $slice[1];
    }

    private static function _buildQueryString($params) 
    {
        return http_build_query($params);

		ksort($params);
		reset($params);
		$pairs = array();
        foreach ($params as $parameter => $value) 
        { 
			$pairs[] = urlencode($parameter) . '=' . $value;
		}

		return implode('&', $pairs);
	}

}//end class

